﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Xml;
using System.Drawing;
using System.Reflection;
using Home.Haushalt.Lib.Element;
using System.Diagnostics;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;

namespace Home.Haushalt.Lib
{
    /// <summary></summary>
    public enum LoginModes { Initial, Normal, Change }

    /// <summary></summary>
    public enum DefaultColumnSets { GridDefault, TabDefault, ForComboBoxControl, balance }

    /// <summary>
    /// 
    /// </summary>
    public static class DAO
    {
        #region EVENTS

        /// <summary></summary>
        public static event EventHandler<HEventArgs> OnStatusMessage;

        /// <summary></summary>
        public static event EventHandler<HEventArgs> OnElementTableSave;

        /// <summary></summary>
        public static event EventHandler<HEventArgs> OnElementTableSaved;

        #endregion EVENTS

        #region CONSTANTS

        /// <summary></summary>
        public const string COLUMNNAME_MONTHNAME_NAME = "name";
        /// <summary></summary>
        public const string COLUMNNAME_MONTHNAME_NUMBER = "number";

        /// <summary></summary>
        public const string COLUMNNAME_VIEWICON_NAME = "name";
        /// <summary></summary>
        public const string COLUMNNAME_VIEWICON_IMAGE = "image";

        /// <summary></summary>
        public static readonly char[] ARRAYSPLITER_IN_XMLATTRIBUT = new char[] { ',' };

        ///// <summary></summary>
        //public const string DEFAULT_LAYOUT_NAME = "griddefault";
        ///// <summary></summary>
        //public const string DEFAULT_TAB_LAYOUT_NAME = "tabdefault";

        #endregion CONSTANTS

        /// <summary></summary>
        public static HCredential Credential = new HCredential();

        /// <summary></summary>
        public static HFile HFile;

        /// <summary></summary>
        public static SettingsCollection Settings;

        /// <summary></summary>
        public static Dictionary<Type, ElementExtensionAttribute> ElementTypes = new Dictionary<Type, ElementExtensionAttribute>();

        /// <summary></summary>
        public static Dictionary<string, Bitmap> ResourceImage = new Dictionary<string, Bitmap>();

        /// <summary></summary>
        public static Dictionary<string, string> UserLogins = new Dictionary<string, string>();

        /// <summary></summary>
        public static MonthbalanceCollection MonthbalanceCollection;

        /// <summary>
        /// 
        /// </summary>
        static DAO()
        {
            //Element-Typen auslesen
            Assembly asm = Assembly.GetExecutingAssembly();
            foreach (Type t in asm.GetTypes())
            {
                if (t.Namespace != null && t.Namespace.Equals("Home.Haushalt.Lib.Element"))
                {
                    var attrs = t.GetCustomAttributes(true);
                    ElementExtensionAttribute attr = null;
                    if (attrs.Length > 0)
                    {
                        attr = attrs[0] as ElementExtensionAttribute;
                    }

                    if (attr != null)
                    {
                        ElementTypes.Add(t, attr);
                    }
                }
            }

            //Settings einlesen
            Settings = new SettingsCollection();

            ////Change-Backup finden bzw erstellen
            //if (_changeBackupDataSet == null)
            //{
            //    _changeBackupDataSet = LoadElementData(ChangeBackup.ElementType);
            //    if (_changeBackupDataSet!=null && _changeBackupDataSet.Tables.Count == 0)
            //    {
            //        _changeBackupDataSet = new DataSet();
            //        _changeBackupDataSet.OpenSchema(ChangeBackup.ElementType);
            //    }
            //}

            LoadResourceImages();

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pass"></param>
        /// <returns></returns>
        public static bool Login(string pass)
        {
            bool result = DAO.Credential.Login(pass);

            if (result)
            {
                HFile = new HFile(Path.Combine(System.Windows.Forms.Application.StartupPath, DAO.Settings.Selected.FileRelPath), Credential.CurrentCry);
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        public static void LoadResourceImages()
        {
            //Resource Images laden
            for (int i = 0; i < 7; i++)
            {
                string wayKey = "way_" + i.ToString();
                ResourceImage.Add(wayKey, (Bitmap)global:: Home.Haushalt.Lib.Properties.Resources.ResourceManager.GetObject(wayKey));
            }
            for (int i = 1; i < 4; i++)
            {
                string directionKey = "direction_" + i;
                ResourceImage.Add(directionKey, (Bitmap)global:: Home.Haushalt.Lib.Properties.Resources.ResourceManager.GetObject(directionKey));
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="newCry"></param>
        /// <returns></returns>
        public static bool CredentialUpdate(HCryptography newCry)
        {
            bool result = true;

            if (OnStatusMessage != null)
                OnStatusMessage(null, new HEventArgs("Daten werden neu verschlüsselt..."));

            //Hier öffnen, da später umgeschlüsselt wird
            DataRowCollection FinAccountRows = LoadElementData(FinAccount.ElementType).Tables[0].Rows;

            int processCount = Settings.Selected.CryptedElements.Count + FinAccountRows.Count;// + documente
            int processIndex = 0;

            //Elemente recrypten
            foreach (string elementTypeName in Settings.Selected.CryptedElements)
            {
                if (OnStatusMessage != null)
                    OnStatusMessage(null, new HEventArgs("Daten werden neu verschlüsselt...", ++processIndex, processCount));

                Type elementType = ElementBase.GetType(elementTypeName);

                LoadElementData(elementType);

                Save(elementType, newCry);
            }

            //Finanzdaten recrypten
            foreach (DataRow row in FinAccountRows)
            {
                FinAccount finAccountObj = new FinAccount(row);
                FinDate finDateObj = new FinDate(finAccountObj);

                if (OnStatusMessage != null)
                    OnStatusMessage(null, new HEventArgs("Daten werden neu verschlüsselt...", ++processIndex, processCount));

                if (Credential.PassAssigned)
                {
                    Credential.CurrentCry.FileDecrypt(finDateObj.FileName);
                }

                newCry.FileEncrypt(finDateObj.FileName);
            }

            //Documente recrypten
            //...

            if (OnStatusMessage != null)
                OnStatusMessage(null, new HEventArgs("Kennwort ist geändert! Ihre Daten sind mit dem neuen Kennwort verschlüsselt."));

            return result;
        }

        #region LOCALIZATION

        ///// <summary></summary>
        //private static XmlDocument _localizationData;

        ///// <summary></summary>
        //private static string _currentLanguageKey = "de-DE";

        ///// <summary></summary>
        //private static XmlDocument LocalizationData
        //{
        //    get
        //    {
        //        if (_localizationData == null)
        //        {
        //            _localizationData = new XmlDocument();
        //            _localizationData.LoadXml("localizationData", "localization");
        //        }
        //        return DAO._localizationData;
        //    }
        //}

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="label"></param>
        ///// <param name="args"></param>
        ///// <returns></returns>
        //public static string Localize(string label, params object[] args)
        //{
        //    string value = "!" + label;


        //    XmlNode labelNode = LocalizationData.SelectSingleNode(
        //        string.Format("Localization/{0}[@{1}='{2}']/{3}[@{1}='{4}']", "Language", "key", _currentLanguageKey, "Label", label)
        //        );

        //    if (labelNode != null)
        //    {
        //        value = labelNode.Attributes["value"].Value.Replace("\\n", "\n");

        //        if (value.Contains("{0}") && args != null && args.Length > 0)
        //        {
        //            value = String.Format(value, args);
        //        }
        //    }


        //    return value;
        //}

        #endregion LOCALIZATION

        #region DATA COLLECTIONS SUPPORT

        /// <summary></summary>
        private static Dictionary<Type, DataTable> _baseTableDictionary;

        /// <summary></summary>
        internal static Dictionary<int, DataTable> FinAccountTableDictionary = new Dictionary<int, DataTable>();

        /// <summary></summary>
        public static Dictionary<Type, DataTable> Tables { get { return _baseTableDictionary; } }

        /// <summary>
        /// 
        /// </summary>
        public static void LoadDecryptDataTables()
        {
            //Parallel.ForEach(ElementTypes.Keys, elementType =>
            foreach (Type elementType in ElementTypes.Keys)
            {
                if (!_baseTableDictionary.ContainsKey(elementType) && !DAO.Settings.Selected.CryptedElements.Contains(elementType.Name))
                {
                    DataSet ds = LoadElementData(elementType);

                    if (ds.Tables != null && !ds.Tables.Count.Equals(0))
                    {
                        _baseTableDictionary.Add(elementType, ds.Tables[0]);
                    }
                }
            };
        }

        /// <summary>
        /// 
        /// </summary>
        public static void LoadMissingDataTables()
        {
            //Parallel.ForEach(ElementTypes.Keys, elementType =>
            foreach (Type elementType in ElementTypes.Keys)
            {
                if (!_baseTableDictionary.ContainsKey(elementType))
                {
                    DataSet ds = LoadElementData(elementType);

                    if (ds.Tables != null && !ds.Tables.Count.Equals(0) && !_baseTableDictionary.ContainsKey(elementType))
                    {
                        _baseTableDictionary.Add(elementType, ds.Tables[0]);
                    }
                }
            };

            MonthbalanceCollection = new MonthbalanceCollection(Tables[typeof(Posten)], Tables[typeof(Date)]);
        }

        /// <summary></summary>
        private static Dictionary<int, string> _monthnames;

        /// <summary></summary>
        public static Dictionary<int, string> Monthnames
        {
            get
            {
                if (_monthnames == null)
                {
                    _monthnames = new Dictionary<int, string>();
                    for (int monthIndex = 1; monthIndex < 13; monthIndex++)
                    {
                        _monthnames.Add(monthIndex, System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.MonthNames[monthIndex - 1]);
                    }
                }
                return _monthnames;
            }
        }

        /// <summary></summary>
        public static DataTable MonthnamesTable
        {
            get
            {
                DataTable tbl = new DataTable();
                tbl.Columns.Add(COLUMNNAME_MONTHNAME_NUMBER, typeof(int));
                tbl.Columns.Add(COLUMNNAME_MONTHNAME_NAME);

                foreach (var month in Monthnames)
                {
                    DataRow row = tbl.NewRow();
                    tbl.Rows.Add(row);
                    row[COLUMNNAME_MONTHNAME_NUMBER] = month.Key;
                    row[COLUMNNAME_MONTHNAME_NAME] = month.Value;
                }

                return tbl;
            }
        }

        /// <summary></summary>
        private static Dictionary<int, string> _weekdaynames;
        /// <summary></summary>
        public static Dictionary<int, string> Weekdaynames
        {
            get
            {
                if (_weekdaynames == null)
                {
                    _weekdaynames = new Dictionary<int, string>();

                    for (int dayIndex = 1; dayIndex < 7; dayIndex++)
                    {
                        _weekdaynames.Add(dayIndex, System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.DayNames[dayIndex]);
                    }

                    _weekdaynames.Add(0, System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.DayNames[0]);
                }
                return _weekdaynames;
            }
        }

        /// <summary></summary>
        private static DataTable _viewIconTable;

        /// <summary></summary>
        public static DataTable ViewIconTable
        {
            get
            {
                if (_viewIconTable == null)
                {
                    _viewIconTable = new DataTable();
                    _viewIconTable.Columns.Add(COLUMNNAME_VIEWICON_IMAGE, typeof(Image));
                    _viewIconTable.Columns.Add(COLUMNNAME_VIEWICON_NAME, typeof(string));

                    foreach (string iconpath in Directory.GetFiles(DAO.HFile.IconsFullPath, "*.*", SearchOption.TopDirectoryOnly))
                    {
                        try
                        {
                            DataRow row = _viewIconTable.NewRow();
                            row[COLUMNNAME_VIEWICON_NAME] = Path.GetFileName(iconpath);
                            row[COLUMNNAME_VIEWICON_IMAGE] = Image.FromFile(iconpath);
                            _viewIconTable.Rows.Add(row);
                        }
                        catch { }
                    }
                }

                return _viewIconTable;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public static void Reset()
        {
            //Geänderte Daten speichern
            if (DAO.DataChanged)
            {
                DAO.Save();
            }

            //Temp-Dateien löschen
            if (DAO.HFile != null)
            {
                DAO.HFile.ClearTemp();
            }

            //Grunddaten-Tabellen löschen
            _baseTableDictionary = new Dictionary<Type, DataTable>();

            //Finanz-Tabellen löschen
            FinAccountTableDictionary.Clear();
        }

        #region I/O

        /// <summary>
        /// 
        /// </summary>
        public static void Save()
        {
            int index = 0;
            int elementCount = _changedElements.Count;

            while (!_changedElements.Count.Equals(0))
            {
                Type elementType = _changedElements[0];

                if (OnElementTableSave != null)
                    OnElementTableSave(elementType, new HEventArgs(LO.Status("ElementTableSaving", LO.ElementList(elementType)), ++index, elementCount));

                Save(elementType);
            }

            //ClearChangeBackupCache();

            if (OnElementTableSaved != null)
                OnElementTableSaved(null, new HEventArgs(LO.Status("ElementTableSaved", index.ToString())));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="elementType"></param>
        private static void Save(Type elementType)
        {
            Save(elementType, Credential.CurrentCry);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="elementType"></param>
        /// <param name="cry"></param>
        private static void Save(Type elementType, HCryptography cry)
        {
            DataSet ds = new DataSet();
            ds.OpenSchema(elementType);

            if (Tables != null && Tables.ContainsKey(elementType))
            {
                ElementBase element = ElementBase.Instantiate(elementType);
                if (element != null)
                {
                    element.FillSavableData(ds.Tables[0]);
                }
            }

            string filename = Path.Combine(DAO.Settings.Selected.DataRelPath, elementType.Name.ToLower() + ".xml");

            if (Settings.Selected.CryptedElements.Contains(elementType.Name))
            {
                ds.WriteXml(filename, cry);
            }
            else
            {
                ds.WriteXml(filename);
            }

            if (_changedElements.Contains(elementType))
            {
                _changedElements.Remove(elementType);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public static void SaveGridLayout()
        {
            if (OnStatusMessage != null)
                OnStatusMessage(typeof(DAO), new HEventArgs(LO.Status("ElementTableSaving", LO.ElementName(typeof(GridLayout)))));

            DAO.Save(typeof(GridLayout));

            if (OnStatusMessage != null)
                OnStatusMessage(typeof(DAO), new HEventArgs(LO.Status("ElementTableSaved", 1)));
        }

        /// <summary>
        /// 
        /// </summary>
        public static void SaveFinAccount()
        {
            DAO.Save(typeof(FinAccount));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="elementType"></param>
        /// <returns></returns>
        public static DataSet LoadElementData(Type elementType)
        {
            if (DAO.Settings.Selected == null) return null;//Für NotRun-Designer

            string filename = Path.Combine(DAO.Settings.Selected.DataRelPath, elementType.Name.ToLower() + ".xml");

            DataSet ds = new DataSet();
            if (File.Exists(filename))
            {
                ds.OpenSchema(elementType);

                if (Settings.Selected.CryptedElements.Contains(elementType.Name) && Credential.PassAssigned)
                {
                    ds.LoadXml(filename, elementType, Credential.CurrentCry);
                }
                else
                {
                    ds.LoadXml(filename, elementType);
                }
            }
            else
            {
                Save(elementType);
                LoadElementData(elementType);
            }

            ElementBase element = ElementBase.Instantiate(elementType);
            element.AfterOpenElementTable(ds.Tables[0]);

            return ds;
        }

        #endregion I/O


        #endregion DATA COLLECTIONS

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public static double GetCurrentSaldo()
        {
            if (DAO.Tables != null && DAO.Tables.ContainsKey(typeof(FinAccount)))
            {
                DataRow[] rows = DAO.Tables[typeof(FinAccount)].Select(string.Format("{0} = true", FinAccount.Fields.IsBaseKonto));

                if (rows.Length > 0)
                {
                    return rows[0].ToDouble(FinAccount.Fields.Saldo);
                }
            }

            return 0;
        }

        /// <summary></summary>
        private static List<Type> _changedElements = new List<Type>();

        /// <summary></summary>
        public static bool DataChanged { get { return _changedElements.Count > 0; } }

        /// <summary>
        /// 
        /// </summary>
        public static void ClearChangedElements()
        {
            _changedElements.Clear();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="element"></param>
        public static void MarkAsChanged(ElementBase element)
        {
            //Element als verändert registrieren
            if (!_changedElements.Contains(element.GetType()))
            {
                _changedElements.Add(element.GetType());
            }
        }

        #region ELEMENT CHANGE BACKUP SUPPORT

        ///// <summary></summary>
        //private static DataSet _changeBackupDataSet;

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="element"></param>
        ///// <param name="row"></param>
        //internal static void SaveToChangeBackup(ElementBase element)
        //{
        //    element.Row.AcceptChanges();

        //    if (Settings.ChangeBackupEnabled)
        //    {
        //        DataTable tbl = _changeBackupDataSet.Tables[element.GetType().Name];

        //        //Evtl. Bestehendes Element aus der Tabelle löschen
        //        DataRow[] rows = tbl.Select(string.Format("{0} = {1} or {0} = ({1} * -1)", ElementBase.ELEMENTPROPERTY_ID, Math.Abs(element.ID)));
        //        foreach (DataRow foundrow in rows)
        //        {
        //            tbl.Rows.Remove(foundrow);
        //        }

        //        //Neues Element hinzufügen
        //        tbl.ImportRow(element.Row);

        //        //Backup speichern
        //        _changeBackupDataSet.WriteXml(Path.Combine(Settings.DataRelPath, ChangeBackup.ElementType.Name.ToLower() + ".xml"));
        //    }

        //    //Element als verändert registrieren
        //    if (!_changedElements.Contains(element.GetType()))
        //    {
        //        _changedElements.Add(element.GetType());
        //    }
        //}

        ///// <summary>
        ///// 
        ///// </summary>
        //public static void ClearChangeBackupCache()
        //{
        //    _changedElements.Clear();

        //    foreach (DataTable changeTbl in _changeBackupDataSet.Tables)
        //    {
        //        changeTbl.Rows.Clear();
        //        changeTbl.AcceptChanges();
        //    }

        //    _changeBackupDataSet.WriteXml(Path.Combine(Settings.DataRelPath, ChangeBackup.ElementType.Name.ToLower() + ".xml"));
        //}

        #endregion ELEMENT CHANGE BACKUP SUPPORT

        /// <summary>
        /// 
        /// </summary>
        /// <param name="elementType"></param>
        /// <param name="select"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static DataRow[] GetRows(Type elementType, string select, params object[] args)
        {
            return Tables[elementType].Select(string.Format(select, args));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="elementType"></param>
        /// <param name="propertyName"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static DataRow[] GetSubRows(Type elementType, string propertyName, object value)
        {
            string statement = Tables[elementType].Columns.Cast<DataColumn>().Where(item => item.ColumnName.StartsWith(propertyName, StringComparison.OrdinalIgnoreCase)).Select(item => string.Format("{0} = {1}", item.ColumnName, value)).ToString(" or ");

            return Tables[elementType].Select(statement);

            //return GetRows(elementType, "{0} = {1}", propertyName, value);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="elementType"></param>
        /// <param name="setName"></param>
        /// <returns></returns>
        public static DataRow[] GetColumnSettings(Type elementType, string setName)
        {
            DataTable coltbl = Tables[Column.ElementType];
            return coltbl.Select(string.Format("{0} = '{1}' and {2} = '{3}'", Column.Fields.Table, elementType.Name, Column.Fields.Name, setName));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="elementType"></param>
        /// <param name="id"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static object GetPropertyAtId(Type elementType, object id, string propertyName)
        {
            DataRow[] rows = Tables[elementType].Select(string.Format("{0} = {1}", ElementBase.ELEMENTPROPERTY_ID, id));

            if (rows != null && rows.Length > 0)
            {
                if (Tables[elementType].Columns.Contains(propertyName))
                {
                    return rows[0][propertyName];
                }
                else
                {
                    Journal.Add(typeof(DAO), ActionResult.Nok, "GetPropertyAtId: Property [" + propertyName + "] gehört nicht zum Element [" + elementType.Name + "]");
                }
            }
            else
            {
                Journal.Add(typeof(DAO), ActionResult.Nok, "GetPropertyAtId: " + elementType.Name + ".Property [" + propertyName + "] ist für id [" + id.ToString() + "] nicht gefunden");
            }

            return null;
        }

        /// <summary></summary>
        private static DataSet _changeBackupDataSet;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="element"></param>
        /// <param name="row"></param>
        public static void SaveToChangeBackup(ElementBase element)
        {
            element.Row.AcceptChanges();

            //if (Settings.ChangeBackupEnabled)
            {
                DataTable tbl = _changeBackupDataSet.Tables[element.GetType().Name];

                //Evtl. Bestehendes Element aus der Tabelle löschen
                DataRow[] rows = tbl.Select(string.Format("{0} = {1} or {0} = ({1} * -1)", ElementBase.ELEMENTPROPERTY_ID, Math.Abs(element.ID)));
                foreach (DataRow foundrow in rows)
                {
                    tbl.Rows.Remove(foundrow);
                }

                //Neues Element hinzufügen
                tbl.ImportRow(element.Row);

                //Backup speichern
                _changeBackupDataSet.WriteXml(Path.Combine(Settings.Selected.DataRelPath, ChangeBackup.ElementType.Name.ToLower() + ".xml"));
            }

            //Element als verändert registrieren
            if (!_changedElements.Contains(element.GetType()))
            {
                _changedElements.Add(element.GetType());
            }
        }

    }

}
